fix(state): gate list fetches on server capability (#1350)#1395
Conversation
The four list-fetching state managers fired their list RPC against every server on connect regardless of advertised capabilities, so a server that doesn't implement a given primitive replied -32601 "Method not found", surfacing in the console on every connect. Mirrors the `tasks`-capability gate added for ManagedRequestorTasksState in #1349. - managedToolsState → gate on capabilities.tools - managedPromptsState → gate on capabilities.prompts - managedResourcesState → gate on capabilities.resources - managedResourceTemplatesState → gate on capabilities.resources (the spec defines no separate resourceTemplates capability; resources/templates/list is part of the resources surface) When the capability is absent each manager sets an empty list, dispatches its change event, and returns — the right semantics for "this server doesn't support X." Tests: existing flow tests now construct their FakeInspectorClient with the relevant capability so the live list path is still exercised; added a "refresh skips listX when capability absent" and a "connect against an X-less server doesn't fire listX" test per manager. Updated the useManagedX peers and resourceSubscriptionsState (which drives a ManagedResourcesState refresh) to advertise the capability too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @cliffhall's task in 2m 40s —— View job Claude Code Review
Verdict: LGTM ✅This is a clean, well-scoped follow-up to #1349. The fix faithfully mirrors the precedent set there, the comments call out exactly why the gate exists (with a -32601 explanation in each), and the test coverage is symmetric across the four managers. Approving in spirit — no changes requested. What I checkedImplementation parity with #1349. The new gate in each of the four managers is identical in shape to if (!client.getCapabilities()?.<cap>) {
this.<list> = [];
this.dispatchTypedEvent("<list>Change", this.<list>);
return this.get<List>();
}Placement is right after the connected guard (correct — there's no point checking capabilities on a disconnected client), and it's reached by both the public Capability mapping for resource templates. Gating Connect-path timing. Confirmed at Test surface. I also grepped the remaining Minor observations (non-blocking)
Nits
|
|
Thanks for the thorough review! No code changes — all three observations are deliberate choices to track the #1349 precedent, but here's the reasoning per point:
On the nits: agreed on both — the per-file -32601 comments are worth keeping un-deduplicated for in-isolation readability, and glad the PR-body motivation was useful. |
…1405) * fix(state): dispatch connect event after capabilities are populated The list-state managers (tools/prompts/resources/templates) refresh on the "connect" event and, since #1395, gate their list RPC on getCapabilities(). But connect() dispatched "connect" before fetchServerInfo() populated this.capabilities, and refresh() runs synchronously up to its first await — so the capability gate executed during the synchronous dispatch, read undefined capabilities (reset to undefined on every disconnect), tripped, and wiped every list to empty on connect. Tools, prompts, resources, and resource templates all vanished against any server. Move the "connect" dispatch to after the awaited fetchServerInfo() so capabilities are in place before any manager refreshes. The unit tests missed this because their FakeInspectorClient has capabilities populated from construction; added an integration regression test wiring a real ManagedToolsState on connect and asserting the tool list is non-empty (fails without the fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(integration): harden connect-race regression test per review Address the two non-blocking suggestions from the PR review: - Replace the 100ms settle sleep with waitForEvent() on the managers' change events, so the test is deterministic against slow CI instead of racing a fixed timeout. - Assert a second affected primitive (ManagedResourcesState alongside ManagedToolsState). The fix is a single shared line, but covering two distinct capabilities guards against a future change gating one primitive differently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #1350.
Problem
#1349 gated
ManagedRequestorTasksState.refreshon the server'staskscapability so we don't firetasks/listagainst servers that don't advertise it (which returned -32601 "Method not found" in the console on every connect to e.g. the NightRider example).The same papercut existed in the other four list-fetching state managers. Almost every server advertises tools/resources/prompts, so it doesn't show up in practice today — but a "tools-only" (or "resources-only") server would surface -32601 in the console on every connect.
Change
Added the same capability gate to each manager's
refresh(), right after the connected-status guard. When the capability is absent it sets an empty list, dispatches the change event, and returns:managedToolsState→capabilities.toolsmanagedPromptsState→capabilities.promptsmanagedResourcesState→capabilities.resourcesmanagedResourceTemplatesState→capabilities.resources(the MCP spec defines no separateresourceTemplatescapability —resources/templates/listis part of the resources surface)Tests
Mirrors the two tests added in #1349's
managedRequestorTasksState.test.ts, per manager:FakeInspectorClientwith the relevant capability so the livelistXpath stays covered.listXwhen capability absent" and "connect against an X-less server doesn't firelistX" (the connect path runsrefresh, so the gate must catch it there too).useManagedXpeers andresourceSubscriptionsState(which drives aManagedResourcesStaterefresh to resolve subscribed URIs) to advertise the capability inbeforeEach.Out of scope
Per the issue: existing intentional -32601 console noise in tests that deliberately probe unsupported methods is left as-is.
Verification
npm run validate(1891 unit tests, +8 new),npm run test:integration(495), andnpm run test:storybook(322) all pass. The integration suite exercises the real-client path against capability-advertising test servers, confirming the gate doesn't suppress real lists.🤖 Generated with Claude Code